//! Lightweight abstraction, a trinary equivalent of `Write` trait allowing access to trinary slices.

use anyhow::{
    bail,
    ensure,
    Result,
};

use iota_streams_core::prelude::{hex, String, };

/// Write
pub trait OStream {
    /// Try advance and panic in case of error.
    fn advance<'a>(&'a mut self, n: usize) -> &'a mut [u8] {
        let r = self.try_advance(n);
        assert!(r.is_ok());
        r.unwrap()
    }

    /// Try put n tbits into the stream, returning a slice to the buffer.
    fn try_advance<'a>(&'a mut self, n: usize) -> Result<&'a mut [u8]>;

    /// Commit advanced buffers to the internal sink.
    fn commit(&mut self);

    /// Dump stream debug info.
    fn dump(&self) -> String {
        String::new()
    }
}

/// Read
pub trait IStream {
    /// Try advance and panic in case of error.
    fn advance<'a>(&'a mut self, n: usize) -> &'a [u8] {
        let r = self.try_advance(n);
        assert!(r.is_ok());
        r.unwrap()
    }

    /// Try get n tbits from the stream, returning a slice to the buffer.
    fn try_advance<'a>(&'a mut self, n: usize) -> Result<&'a [u8]>;

    /// Commit advanced buffers from the internal sources.
    fn commit(&mut self);

    /// Dump stream debug info.
    fn dump(&self) -> String {
        String::new()
    }
}

impl<'b> OStream for &'b mut [u8] {
    fn try_advance<'a>(&'a mut self, n: usize) -> Result<&'a mut [u8]> {
        ensure!(n <= self.len(), "Output slice too short.");
        let (head, tail) = (*self).split_at_mut(n);
        unsafe {
            *self = core::mem::transmute::<&'a mut [u8], &'b mut [u8]>(tail);
        }
        Ok(head)
    }
    fn commit(&mut self) {}
    fn dump(&self) -> String {
        format!("{}", hex::encode(self))
    }
}

impl<'b> IStream for &'b [u8] {
    fn try_advance<'a>(&'a mut self, n: usize) -> Result<&'a [u8]> {
        ensure!(n <= self.len(), "Input slice too short.");
        let (head, tail) = (*self).split_at(n);
        unsafe {
            *self = core::mem::transmute::<&'a [u8], &'b [u8]>(tail);
        }
        Ok(head)
    }
    fn commit(&mut self) {}
    fn dump(&self) -> String {
        format!("{}", hex::encode(self))
    }
}

pub struct NoOStream;

impl OStream for NoOStream {
    fn advance<'a>(&'a mut self, n: usize) -> &'a mut [u8] {
        assert!(false, "Advance can't be implemented for NoOStream");
        self.try_advance(n).unwrap()
    }
    fn try_advance<'a>(&'a mut self, _n: usize) -> Result<&'a mut [u8]> {
        bail!("Advance can't be implemented for NoOStream")
    }
    fn commit(&mut self) {}
}

pub struct NoIStream;

impl IStream for NoIStream {
    fn advance<'a>(&'a mut self, n: usize) -> &'a [u8] {
        assert!(false, "Advance can't be implemented for NoIStream");
        self.try_advance(n).unwrap()
    }
    fn try_advance<'a>(&'a mut self, _n: usize) -> Result<&'a [u8]> {
        bail!("Advance can't be implemented for NoIStream")
    }
    fn commit(&mut self) {}
}
